Let's make several assumptions before we really start.
- You know and love Groovy. Otherwise you'd hardly invest your valuable time into studying a Groovy concurrency library.
- If you don't want to use Groovy, you are prepared to pay the inevitable verbosity tax on using GPars from Java
- You target multi-core hardware with your code
- You use or want to use Groovy or Java to write concurrent code.
- You have at least some understanding that in concurrent code some things can happen at any time in any order and often more of them at the same time.
That's about it. Let's roll the ball forward.Brief overview
GPars aims to bring several useful concurrency abstractions to Java and Groovy developers. It's becoming obvious that dealing
with concurrency on the thread/synchronized/lock level, as provided by the JVM, is way too low level to be safe and comfortable.
Many high-level concepts, like actors or dataflow concurrency have been around for quite some time, since parallel computers
had been in use in computer centers long before multi-core chips hit the hardware mainstream. Now, however, it's the time to
adopt and test these abstractions for the mainstream software industry.The concepts available in GPars can be categorized into three main groups:
- Code-level helpers - constructs that can be applied to small parts of the code-base such as individual algorithms or data structures without any major changes in the overall project architecture
- Parallel Collections
- Asynchronous Processing
- Fork/Join (Divide/Conquer)
- Architecture-level concepts - constructs that need to be taken into account when designing the project structure
- Actors
- Communicating Sequential Processes
- Dataflow Concurrency
- Shared Mutable State Protection - although about 95 of current use of shared mutable state can be avoided using proper abstractions, good abstractions are still necessary for the remaining 5% use cases, when shared mutable state can't be avoided
- Agents
- Software Transactional Memory (not implemented in GPars yet) would also belong to this group
There are several ways to add GPars to your project. Either download and add all the jar files manually, specify a dependency in Maven, Ivy or Gradle build files or use Grape.
If you're building a Grails or a Griffon application, you can leverage the appropriate plugins to fetch the jar files for you.Dependency resolution
GPars requires two compulsory dependencies - the jsr166y and the extra166y
jar files, which are the artifacts of the JSR-166 initiative . These must be on the classpath.<dependency>
<groupId>org.codehaus.jsr166-mirror</groupId>
<artifactId>jsr166y</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.jsr166-mirror</groupId>
<artifactId>extra166y</artifactId>
<version>1.7.0</version>
</dependency>
GPars defines both of the dependencies in its own descriptor, so both dependencies should be taken care of automatically,
if you use Gradle, Maven, Ivy or other type of automatic dependency resolution tool.Please visit the Integration page of the project for details.
Once you got setup, try the following Groovy script to test that your setup is functional. For Java, see below.
import static groovyx.gpars.actor.Actors.actor/**
* A demo showing two cooperating actors. The decryptor decrypts received messages and replies them back.
* The console actor sends a message to decrypt, prints out the reply and terminates both actors.
* The main thread waits on both actors to finish using the join() method to prevent premature exit,
* since both actors use the default actor group, which uses a daemon thread pool.
* @author Dierk Koenig, Vaclav Pech
*/def decryptor = actor {
loop {
react {message ->
if (message instanceof String) reply message.reverse()
else stop()
}
}
}def console = actor {
decryptor.send 'lellarap si yvoorG'
react {
println 'Decrypted message: ' + it
decryptor.send false
}
}[decryptor, console]*.join()
You should get a message "Decrypted message: Groovy is parallel" printed out on the console when you run the code.GPars - a Java library
Although GPars has been primarily designed for the Groovy programming language, the solid technical foundation plus good performance characteristics
make GPars a good Java library as well. Since most of GPars is written in Java, there is no extra performance penalty Java applications
would pay when using GPars.For details please refer to the Java API section.
To quick-test your integration through Java API, run the following Java actor code:import groovyx.gpars.MessagingRunnable;
import groovyx.gpars.actor.DynamicDispatchActor;public class StatelessActorDemo {
public static void main(String[] args) throws InterruptedException {
final MyStatelessActor actor = new MyStatelessActor();
actor.start();
actor.send("Hello");
actor.sendAndWait(10);
actor.sendAndContinue(10.0, new MessagingRunnable<String>() {
@Override protected void doRun(final String s) {
System.out.println("Received a reply " + s);
}
});
}
}class MyStatelessActor extends DynamicDispatchActor {
public void onMessage(final String msg) {
System.out.println("Received " + msg);
replyIfExists("Thank you");
} public void onMessage(final Integer msg) {
System.out.println("Received a number " + msg);
replyIfExists("Thank you");
} public void onMessage(final Object msg) {
System.out.println("Received an object " + msg);
replyIfExists("Thank you");
}
}
We follow certain conventions in the code samples. Understanding these may help you read and comprehend GPars code samples better.
- The leftShift operator << has been overloaded on actors, agents and dataflow expressions (both variables and streams) to mean send a message or assign a value.
myActor << 'message'myAgent << {account -> account.add('5 USD')}myDataflowVariable << 120332
- On actors and agents the default call() method has been also overloaded to mean send . So sending a message to an actor or agent may look like a regular method call.
myActor "message"myAgent {house -> house.repair()}
- The rightShift operator >> in GPars has the when bound meaning. So
myDataflowVariable >> {value -> doSomethingWith(value)}
will schedule the closure to run only after myDataflowVariable is bound to a value, with the value as a parameter.In samples we tend to statically import frequently used factory methods:
- GParsPool.withPool()
- GParsPool.withExistingPool()
- GParsExecutorsPool.withPool()
- GParsExecutorsPool.withExistingPool()
- Actors.actor()
- Actors.reactor()
- Actors.fairReactor()
- Actors.messageHandler()
- Actors.fairMessageHandler()
- Agent.agent()
- Agent.fairAgent()
- Dataflow.task()
- Dataflow.operator()
It is more a matter of style preferences and personal taste, but we think static imports make the code more compact and readable.
Adding the GPars jar files to your project or defining the appropriate dependencies in pom.xml should be enough to get you started with GPars in your IDE.GPars DSL recognition
IntelliJ IDEA in both the free Community Edition and the commercial Ultimate Edition will recognize the GPars domain specific languages,
complete methods like eachParallel() , reduce() or callAsync() and validate them. GPars uses the GroovyDSL
mechanism, which teaches IntelliJ IDEA the DSLs as soon as the GPars jar file is added to the project.Here you could find basic guide-lines helping you decide on which GPars abstraction to apply to your code at hands.
- You're looking at a collection, which needs to be iterated or processed using one of the many beautiful Groovy collections method, like each() , collect() , find() and such. Proposing that processing each element of the collection is independent of the other items, using GPars parallel collections can be recommended.
- If you have a long-lasting calculation , which may safely run in the background, use the asynchronous invocation support in GPars. You can also benefit, if your long-calculating closures need to be passed around and yet you'd like them not to block the main application thread.
- You need to parallelize an algorithm at hand. You can identify sub-tasks and you're happy to explicitly express the options for parallelization. You create internally sequential tasks, each of which can run concurrently with the others, providing they all have a way to exchange data at some well-defined moments through communication channels with safe semantics. Use GPars dataflow tasks, variables and streams.
- You can't avoid shared mutable state. Multiple threads will be accessing shared data and (some of them) modifying the data. Traditional locking and synchronized approach feels too risky or unfamiliar. Go for agents, which will wrap your data and serialize all access to it.
- You're building a system with high concurrency demands. Tweaking a data structure here or task there won't cut it. You need to build the architecture from the ground up with concurrency in mind. Message-passing might be the way to go.
- Groovy CSP will give you highly deterministic and composable model for concurrent processes.
- If you're trying to solve a complex data-processing problem, consider GPars dataflow operator to build a data flow network.
- Actors will shine if you need to build a general-purpose, highly concurrent and scalable architecture.
Now you may have a better idea of what concepts to use on your current project. Go and check out more details on them in the User Guide.Again, the new release, this time GPars 0.12, introduces a lot of gradual enhancements and improvements on top of the previous release.Check out the JIRA release notesProject changes
See the Breaking Changes listing for the list of breaking changes.
Asynchronous functions
- The asyncFun() method now creates composable asynchronous functions
- The @AsyncFun annotation can be used to create composable asynchronous functions stored in fields in a more declarative way
Parallel collections
- Collections can now repeatedly be made transparently concurrent or sequential using makeConcurrent() and makeSequential() methods
- Renamed makeTransparent() to makeConcurrent()
Fork / Join
- A few new demos illustrating Fork/Join applicability to recursive functions have been added
- Leveraging the new and efficient implementation of the jsr-166y (aka Java 7) Fork/Join library
- The runChildDirectly() method allowing to mix asynchronous and synchronous child task execution
Actors
- Active Objects wrapping actors with an OO facade
- Enhanced DynamicDispatchActor's API for dynamic message handler registration
- Added BlockingActor to allow for non-continuation style actors
- Removed the deprecated actor classes
Dataflow
Agent
Stm
- Initial support for Stm through Multiverse was added
Other
- Switched to the most recent Java 7 Fork/Join library to ensure compatibility with future JDKs
- Raised the Groovy level used for compilation to 1.7
- Created a pdf version of the user guide
- An update to the stand-alone maven-based Java API demo application was added to show GPars integration and use from Java
- Added numerous code examples and demos
- Enhanced project documentation
Renaming hints
- The makeTransparent() method that forces concurrent semantics to iteration methods (each, collect, find, etc.) has been renamed to makeConcurrent()
- Capitalization has changed in the names of dataflow classes DataFlow -> Dataflow e.g. DataFlowVariable is now called DataflowVariable
- The DataFlowPoisson class has been renamed to PoisonPill
Using GPars is very addictive, I guarantee. Once you get hooked you won't be able to code without it.
May the world force you to write code in Java, you will still be able to benefit from most of GPars features.Java API specifics
Some parts of GPars are irrelevant in Java and it is better to use the underlying Java libraries directly:
- Parallel Collection - use jsr-166y library's Parallel Array directly
- Fork/Join - use jsr-166y library's Fork/Join support directly
- Asynchronous functions - use Java executor services directly
The other parts of GPars can be used from Java just like from Groovy, although most will miss the Groovy DSL capabilities.GPars Closures in Java API
To overcome the lack of closures as a language element in Java and to avoid forcing users to use Groovy closures directly
through the Java API, a few handy wrapper classes have been provided to help you define callbacks, actor body or dataflow tasks.
- groovyx.gpars.MessagingRunnable - used for single-argument callbacks or actor body
- groovyx.gpars.ReactorMessagingRunnable - used for ReactiveActor body
- groovyx.gpars.DataflowMessagingRunnable - used for dataflow operators' body
These classes can be used in all places GPars API expects a Groovy closure.Actors
The DynamicDispatchActor as well as the ReactiveActor classes can be used just like in Groovy:import groovyx.gpars.MessagingRunnable;
import groovyx.gpars.actor.DynamicDispatchActor; public class StatelessActorDemo {
public static void main(String[] args) throws InterruptedException {
final MyStatelessActor actor = new MyStatelessActor();
actor.start();
actor.send("Hello");
actor.sendAndWait(10);
actor.sendAndContinue(10.0, new MessagingRunnable<String>() {
@Override protected void doRun(final String s) {
System.out.println("Received a reply " + s);
}
});
}
} class MyStatelessActor extends DynamicDispatchActor {
public void onMessage(final String msg) {
System.out.println("Received " + msg);
replyIfExists("Thank you");
} public void onMessage(final Integer msg) {
System.out.println("Received a number " + msg);
replyIfExists("Thank you");
} public void onMessage(final Object msg) {
System.out.println("Received an object " + msg);
replyIfExists("Thank you");
}
}
Although there are not many differences between Groovy and Java GPars use, notice, the callbacks instantiating the MessagingRunnable class in place for a groovy closure.import groovy.lang.Closure;
import groovyx.gpars.ReactorMessagingRunnable;
import groovyx.gpars.actor.Actor;
import groovyx.gpars.actor.ReactiveActor;public class ReactorDemo {
public static void main(final String[] args) throws InterruptedException {
final Closure handler = new ReactorMessagingRunnable<Integer, Integer>() {
@Override protected Integer doRun(final Integer integer) {
return integer * 2;
}
};
final Actor actor = new ReactiveActor(handler);
actor.start(); System.out.println("Result: " + actor.sendAndWait(1));
System.out.println("Result: " + actor.sendAndWait(2));
System.out.println("Result: " + actor.sendAndWait(3));
}
}
Convenience factory methods
Obviously, all the essential factory methods to build actors quickly are available where you'd expect them.import groovy.lang.Closure;
import groovyx.gpars.ReactorMessagingRunnable;
import groovyx.gpars.actor.Actor;
import groovyx.gpars.actor.Actors;public class ReactorDemo {
public static void main(final String[] args) throws InterruptedException {
final Closure handler = new ReactorMessagingRunnable<Integer, Integer>() {
@Override protected Integer doRun(final Integer integer) {
return integer * 2;
}
};
final Actor actor = Actors.reactor(handler); System.out.println("Result: " + actor.sendAndWait(1));
System.out.println("Result: " + actor.sendAndWait(2));
System.out.println("Result: " + actor.sendAndWait(3));
}
}
Agents
import groovyx.gpars.MessagingRunnable;
import groovyx.gpars.agent.Agent; public class AgentDemo {
public static void main(final String[] args) throws InterruptedException {
final Agent counter = new Agent<Integer>(0);
counter.send(10);
System.out.println("Current value: " + counter.getVal());
counter.send(new MessagingRunnable<Integer>() {
@Override protected void doRun(final Integer integer) {
counter.updateValue(integer + 1);
}
});
System.out.println("Current value: " + counter.getVal());
}
}
Dataflow Concurrency
Both DataflowVariables and DataflowQueues can be used from Java without any hiccups. Just avoid the handy overloaded operators
and go straight to the methods, like bind , whenBound , getVal and other.
You may also continue using dataflow tasks passing to them instances of Runnable or Callable just like groovy Closure .import groovyx.gpars.MessagingRunnable;
import groovyx.gpars.dataflow.DataflowVariable;
import groovyx.gpars.group.DefaultPGroup;import java.util.concurrent.Callable;public class DataflowTaskDemo {
public static void main(final String[] args) throws InterruptedException {
final DefaultPGroup group = new DefaultPGroup(10); final DataflowVariable a = new DataflowVariable(); group.task(new Runnable() {
public void run() {
a.bind(10);
}
}); final DataflowVariable result = group.task(new Callable() {
public Object call() throws Exception {
return (Integer)a.getVal() + 10;
}
}); result.whenBound(new MessagingRunnable<Integer>() {
@Override protected void doRun(final Integer integer) {
System.out.println("arguments = " + integer);
}
}); System.out.println("result = " + result.getVal());
}
}
Dataflow operators
The sample below should illustrate the main differences between Groovy and Java API for dataflow operators.
- Use the convenience factory methods accepting list of channels to create operators or selectors
- Use DataflowMessagingRunnable to specify the operator body
- Call getOwningProcessor() to get hold of the operator from within the body in order to e.g. bind output values
import groovyx.gpars.DataflowMessagingRunnable;
import groovyx.gpars.dataflow.Dataflow;
import groovyx.gpars.dataflow.DataflowQueue;
import groovyx.gpars.dataflow.operator.DataflowProcessor;import java.util.Arrays;
import java.util.List;public class DataflowOperatorDemo {
public static void main(final String[] args) throws InterruptedException {
final DataflowQueue stream1 = new DataflowQueue();
final DataflowQueue stream2 = new DataflowQueue();
final DataflowQueue stream3 = new DataflowQueue();
final DataflowQueue stream4 = new DataflowQueue(); final DataflowProcessor op1 = Dataflow.selector(Arrays.asList(stream1), Arrays.asList(stream2), new DataflowMessagingRunnable(1) {
@Override protected void doRun(final Object[] objects) {
getOwningProcessor().bindOutput(2*(Integer)objects[0]);
}
}); final List secondOperatorInput = Arrays.asList(stream2, stream3); final DataflowProcessor op2 = Dataflow.operator(secondOperatorInput, Arrays.asList(stream4), new DataflowMessagingRunnable(2) {
@Override protected void doRun(final Object[] objects) {
getOwningProcessor().bindOutput((Integer) objects[0] + (Integer) objects[1]);
}
}); stream1.bind(1);
stream1.bind(2);
stream1.bind(3);
stream3.bind(100);
stream3.bind(100);
stream3.bind(100);
System.out.println("Result: " + stream4.getVal());
System.out.println("Result: " + stream4.getVal());
System.out.println("Result: " + stream4.getVal());
op1.stop();
op2.stop();
}
}
Performance
In general, GPars overhead is identical irrespective of whether you use it from Groovy or Java and tends to be very low.
GPars actors, for example, can compete head-to-head with other JVM actor options, like Scala actors.Since Groovy code in general runs slower than Java code, mainly due to dynamic method invocation, you might consider writing
your code in Java to improve performance. Typically numeric operations or frequent fine-grained method calls within a task or actor body
may benefit from a rewrite into Java.Prerequisites
All the GPars integration rules apply to Java projects just like they do to Groovy projects. You only need to include the groovy distribution jar file in your project and all is clear to march ahead.
You may also want to check out the sample Java Maven project to get tips on how to integrate GPars into a maven-based pure Java application - Sample Java Maven Project